<?php 

/**
 * A geocoder adapter for the Multimap Geocode Open API
 * {@link http://www.multimap.com/openapidocs/1.2/web_service/ws_geocoding.htm}
 *
 * @category Noginn
 * @package Noginn_Geocoder
 */
class Noginn_Geocoder_Adapter_Multimap extends Noginn_Geocoder_Adapter_Abstract
{
    /** Base Multimap Geocode Open API URI */
    const SERVICE_URI = 'http://developer.multimap.com/API/geocode/1.2/';
    
    /**
     * The API key
     *
     * @var string
     */
    protected $_apiKey;

    /**
     * The HTTP client to use for API requests
     *
     * @var Zend_Http_Client
     */
    protected $_httpClient;

    /**
     * Constructor
     *
     * @param array|Zend_Config $config 
     * @return void
     */
    public function __construct($config = array())
    {
        iconv_set_encoding('output_encoding', 'UTF-8');
        iconv_set_encoding('input_encoding', 'UTF-8');
        iconv_set_encoding('internal_encoding', 'UTF-8');

        if (array_key_exists('apiKey', $config)) {
            $this->setApiKey($config['apiKey']);
        }
    }

    /**
     * Set the API key
     *
     * @param string $apiKey 
     * @return void
     */
    public function setApiKey($apiKey)
    {
        $this->_apiKey = $apiKey;

        return $this;
    }

    /**
     * Return the API key
     *
     * @return string
     */
    public function getApiKey()
    {
        return $this->_apiKey;
    }

    /**
     * Return the HTTP client. 
     * If not set then create a new instance of Zend_Http_Client.
     *
     * @return Zend_Http_Client
     */
    public function getHttpClient()
    {
        if (is_null($this->_httpClient)) {
            $this->_httpClient = new Zend_Http_Client(self::SERVICE_URI);
        }

        return $this->_httpClient;
    }
    
    /**
     * Specify the Zend_Http_Client object to use
     *
     * @param Zend_Http_Client $httpClient 
     * @return void
     */
    public function setHttpClient(Zend_Http_Client $httpClient)
    {
        $httpClient->setUri(self::SERVICE_URI);
        $this->_httpClient = $httpClient;
    }

    /**
     * Geocode the address
     *
     * @param string|array $address 
     * @return Noginn_Geocoder_LocationList
     */
    public function geocode($address)
    {
        $address = $this->_buildQuery($address);
        
        // Check if the current location list was cached yet
        $cache = Noginn_Geocoder::getCache();
        
        if ($cache !== null) {
            $cacheId = 'Noginn_Geocoder_Multimap_' . md5($address);

            if (false !== ($locationList = $cache->load($cacheId))) {
                return $locationList;
            }
        }
        
        $httpClient = $this->getHttpClient();
        $httpClient->resetParameters();
        $httpClient->setParameterGet('qs', $this->_buildQuery($address));
        $response = $httpClient->request('GET');

        // Check for HTTP client request errors
        if ($response->isError()) {
            throw new Zend_Exception('An error occurred sending the geocode request. Status code: ' . $response->getStatus());
        }

        $xml = new SimpleXMLElement($response->getBody());
        
        // Check for API errors
        if (isset($xml->ErrorCode)) {
            switch ($xml->ErrorCode) {
                case 'MM_GEOCODE_FAILED':
                    throw new Zend_Exception('The geocoding request failed');
                    break;
                    
                /*case 'MM_GEOCODE_MULTIPLE_MATCHES':
                    throw new Zend_Exception('The input geocoding parameters found multiple matches');
                    break;*/
                    
                case 'MM_GEOCODE_NO_MATCHES':
                    throw new Zend_Exception('The input address cannot be geocoded');
                    break;
                    
                case 'MM_GEOCODE_REQUIRED_PARAMETERS_MISSING':
                    throw new Zend_Exception('The input address has missing parameters');
                    break;
                
                case 'MM_API_ERROR_INTERNAL_ERROR':
                    throw new Zend_Exception('An unexpected internal error occured');
                    break;
                    
                case 'MM_API_ERROR_UNSUPPORTED_SERVICE':
                    throw new Zend_Exception('Incorrect service URL');
                    break;

                case 'MM_API_REQUIRED_PARAMETERS_MISSING':
                    throw new Zend_Exception('An address has been supplied with some of its parameters missing');
                    break;
                
                case 'MM_ERROR_SERVICE_NOT_PROVISIONED':
                case 'MM_API_ERROR_SERVICE_NOT_PROVISIONED':
                    throw new Zend_Exception('Geocode service not provisioned');
                    break;
            }
        }
        
        // Parse the result set
        $locationList = new Noginn_Geocoder_LocationList();
        foreach ($xml->Location as $result) {
            $location = new Noginn_Geocoder_Location();
            
            $location->accuracy = (int) $result['geocodeQuality'];
            $location->latitude = (float) $result->Point->Lat;
            $location->longitude = (float) $result->Point->Lon;
            
            $count = 0;
            foreach ($result->Address->Areas->Area as $area) {
                if ($count == 0) {
                    $location->region = (string) $area;
                }
                else if ($count == 1) {
                    $location->city = (string) $area;
                }
                else if ($count == 2) {
                    $location->street = (string) $area;
                }
                $count++;
            }
            
            $location->postalCode = (string) $result->Address->PostalCode;
            $location->country = (string) $result->Address->CountryCode;
            
            $location->address = (string) $result->Address->DisplayName;
            
            $locationList->addLocation($location);
        }
        
        // If caching is enabled, cache the current result
        if ($cache !== null) {
            $cache->save($locationList, $cacheId);
        }
        
        return $locationList;
    }
}